#include "../../include/pagelib/PageLib.h"
#include "../../include/pagelib/DirScanner.h"
#include "../../include/pagelib/Configuration.h"
#include "../../include/pagelib/MyLogger.h"
#include "../../include/pagelib/tinyxml2.h"

#include "../../lib/simhash/Simhasher.hpp"
#include "../../lib/cppjieba/Jieba.hpp"

#include <regex>
#include <fstream>
#include <utility>

using namespace tinyxml2;
using namespace simhash;

PageLib::PageLib(const string &cropus)
    : _dirScanner(*DirScanner::getInstance())
{
    _dirScanner.scan(Configuration::getInstance()->getMapValue("corpusPath"));
}

PageLib::~PageLib()
{
}

// 创建网页库相关的数据
void PageLib::createPageLib()
{
    // 创建网页库
    createWebLib();
    // 创建网页偏移量库
    createOffsetLib();
    // 创建倒排索引库
    createInvertIndexLib();
}

// 将数据存储到文件
void PageLib::storePageLibToFile()
{
    // 存储网页库
    storeWebPageLib();
    // 存储网页偏移库
    storeOffsetLib();
    // 存储倒排索引库
    storeInvertIndexLib();
}

// 创建网页库
void PageLib::createWebLib()
{
    // 获取到所有的网页文件
    vector<string> files = _dirScanner.getFiles();
    if (files.empty())
    {
        LogError("Error: No files! ");
        return;
    }

    // 读取每一篇网页，进行清洗
    LogInfo("Starting to read and clean RSS files.");
    for (auto &file : files)
    {
        readFileClean(file);
    }
    LogInfo("Finished reading and cleaning RSS files. Total files processed: " + to_string(files.size()));

    // 网页去重
    LogInfo("Starting SimHash deduplication.");
    LogInfo("Before deduplication, RSS size = %zu", _rss.size());
    simHash();
    LogInfo("After deduplication, RSS size = %zu", _rss.size());
    LogInfo("Finished SimHash deduplication.");

    // 创建格式化网页库
    LogInfo("Starting to create formatted web pages.");
    createFormatWebPage();
    LogInfo("Finished creating formatted web pages.");
}

// 创建网页偏移库
void PageLib::createOffsetLib()
{
    if (_pages.empty())
    {
        LogInfo("PageLib::createOffsetLib: _pages is empty, no offset data to create.");
        return;
    }

    int index = 0;                     // 文章的 docid
    size_t pos = 0;                    // 位置
    size_t totalPages = _pages.size(); // 总网页数

    LogInfo("PageLib::createOffsetLib: Start creating offset library for " + to_string(totalPages) + " pages.");

    // 遍历 _pages，计算每个网页的偏移量和大小
    for (auto &page : _pages)
    {
        // 记录当前网页的偏移量和大小
        _offsetLib[index + 1] = pair<int, int>(pos, page.size());

        // 更新累加偏移量
        pos += page.size();
        ++index;
    }
    LogInfo("PageLib::createOffsetLib: Offset library created successfully.");
}

// 创建倒排索引库(TF/IDF)
void PageLib::createInvertIndexLib()
{
    // 获取配置文件
    unordered_map<string, string> configMap = Configuration::getInstance()->getConfigMap();

    // 初始化分词工具
    cppjieba::Jieba jieba(configMap["DICT_PATH"].c_str(),
                          configMap["HMM_PATH"].c_str(),
                          configMap["USER_DICT_PATH"].c_str(),
                          configMap["IDF_PATH"].c_str(),
                          configMap["STOP_WORD_PATH"].c_str());

    // 词频（TF）：记录每个词在不同文档中的出现次数
    unordered_map<string, unordered_map<int, int>> TermFreq;
    // 文档频率（DF）：记录包含某个词的所有文档ID
    unordered_map<string, set<int>> DoctFreq;
    // 逆文档频率（IDF）：衡量词语的重要性
    unordered_map<string, double> InvDocFreq;

    // 读取停止词列表
    unordered_set<string> stopWordSet = readStopWord(configMap["STOP_WORD_PATH"]);

    vector<int> wordCountPerDocument(_pages.size());   // 存储每篇文章中的总词数

    // 统计TF、DF
    for (size_t docIndex = 0; docIndex < _pages.size(); ++docIndex)
    {
        vector<string> words;     // 分词结果
        jieba.Cut(_pages[docIndex], words, true);

        for (const string& word : words)
        {
            // 过滤停用词
            if (stopWordSet.find(word) != stopWordSet.end())
            {
                continue;
            }

            // 更新词频（TF）
            auto termFrequencyIt = TermFreq.find(word);
            if (termFrequencyIt == TermFreq.end())  // 该词在当前文档中首次出现
            {
                TermFreq[word] = unordered_map<int, int>({{docIndex + 1, 1}});
            }
            else  // 该词在当前文档中已出现过
            {
                (termFrequencyIt->second)[docIndex + 1]++;
            }

            // 更新文档频率（DF）
            DoctFreq[word].insert(docIndex + 1);
            wordCountPerDocument[docIndex]++;
        }
    }

    // 计算逆文档频率（IDF）
    for (const auto& documentFrequencyEntry : DoctFreq)
    {
        const string& word = documentFrequencyEntry.first;
        const set<int>& documentSet = documentFrequencyEntry.second;

        // 如果该词在所有文档中都出现过，则跳过（IDF为0）
        if (documentSet.size() == _pages.size())
        {
            continue;
        }

        // 计算IDF
        InvDocFreq[word] = log2(_pages.size() / (documentSet.size() + 1));
    }

    // 计算权重并构建倒排索引
    for (const auto& termFrequencyEntry : TermFreq)
    {
        const string& word = termFrequencyEntry.first;
        const unordered_map<int, int>& termFrequencyPerDoc = termFrequencyEntry.second;

        // 跳过在所有文档中都出现的词或长度过长的词
        if (DoctFreq[word].size() == _pages.size() || word.size() > 24)
        {
            continue;
        }

        for (const auto& termFrequencyPerDocEntry : termFrequencyPerDoc)
        {
            int docId = termFrequencyPerDocEntry.first;
            int termFrequency = termFrequencyPerDocEntry.second;

            // 计算权重,并做归一化处理（除以该篇文章的总词数）
            double weight = InvDocFreq[word] * termFrequency / wordCountPerDocument[docId - 1];
            _invertIndexLib[word].push_back(make_pair(docId, weight));
        }
    }
}

// 存储网页库
void PageLib::storeWebPageLib()
{
    // 获取文件名
    string filename = Configuration::getInstance()->getMapValue("formatWebpage.dat");
    if (filename.empty())
    {
        LogError("Configuration error: Key 'formatWebpage.dat' not found or value is empty.");
        return;
    }

    // 打开文件
    ofstream ofs(filename);
    if (!ofs)
    {
        LogError("File error: Failed to open file '" + filename + "' for writing.");
        return;
    }

    // 写入网页库数据
    size_t recordCount = 0; // 记录写入的记录数
    for (const auto &page : _pages)
    {
        ofs << page;
        ++recordCount;
    }

    ofs.close();
    LogInfo("Successfully stored " + to_string(recordCount) + " records to webpage library file: " + filename);
}

// 存储网页偏移库
void PageLib::storeOffsetLib()
{
    // 获取文件名
    string filename = Configuration::getInstance()->getMapValue("offsetLib.dat");
    if (filename.empty())
    {
        LogError("Configuration error: Key 'offsetLib.dat' not found or value is empty.");
        return;
    }

    // 打开文件
    ofstream ofs(filename);
    if (!ofs)
    {
        LogError("File error: Failed to open file '" + filename + "' for writing.");
        return;
    }

    // 写入偏移库数据
    size_t recordCount = 0; // 记录写入的记录数
    for (const auto &ele : _offsetLib)
    {
        ofs << ele.first << " " << ele.second.first << " " << ele.second.second << endl;
        ++recordCount;
    }

    // 关闭文件
    ofs.close();

    // 记录成功日志
    LogInfo("Successfully stored " + to_string(recordCount) + " records to offset library file: " + filename);
}

// 存储倒排索引库
void PageLib::storeInvertIndexLib()
{
    // 获取文件名
    string filename = Configuration::getInstance()->getMapValue("invertIndex.dat");
    if (filename.empty())
    {
        LogError("Configuration error: Key 'invertIndex.dat' not found or value is empty.");
        return;
    }

    // 打开文件
    ofstream ofs(filename);
    if (!ofs)
    {
        LogError("File error: Failed to open file '" + filename + "' for writing.");
        return;
    }

    // 写入倒排索引数据
    size_t recordCount = 0; // 记录写入的记录数
    for (const auto &ele : _invertIndexLib)
    {
        ofs << ele.first << " ";
        auto it = ele.second.begin();
        while (it != ele.second.end())
        {
            ofs << it->first << " " << it->second << " ";
            ++it; // 修复：更新迭代器
        }
        ofs << endl;
        ++recordCount;
    }

    ofs.close();
    LogInfo("Successfully stored " + to_string(recordCount) + " records to invert index library file: " + filename);
}

// 读取网页内容，进行清洗
void PageLib::readFileClean(const string &filename)
{
    // 读取网页
    XMLDocument doc;
    doc.LoadFile(filename.c_str());
    if (doc.ErrorID())
    {
        LogInfo("Error: Failed to load file " + filename);
        return;
    }

    // 获取 <item> 节点
    XMLElement *itemNode = doc.FirstChildElement("rss")->FirstChildElement("channel")->FirstChildElement("item");
    if (!itemNode)
    {
        LogInfo("Error: No <item> node found in the XML file.");
        return;
    }

    // 遍历所有 <item> 节点
    while (itemNode)
    {
        string title, link, description, content;

        // 提取 title
        XMLElement *titleNode = itemNode->FirstChildElement("title");
        if (titleNode && titleNode->GetText())
        {
            title = titleNode->GetText();
        }

        // 提取 link
        XMLElement *linkNode = itemNode->FirstChildElement("link");
        if (linkNode && linkNode->GetText())
        {
            link = linkNode->GetText();
        }

        // 提取 description 并清洗
        XMLElement *descNode = itemNode->FirstChildElement("description");
        if (descNode && descNode->GetText())
        {
            content = cleanText(descNode->GetText());
        }

        // 提取 content:encoded 或 content 并清洗
        XMLElement *contentNode = itemNode->FirstChildElement("content:encoded");
        if (!contentNode)
        {
            contentNode = itemNode->FirstChildElement("content");
        }
        if (contentNode && contentNode->GetText())
        {
            content = cleanText(contentNode->GetText());
        }

        // 如果 description 或 content 不为空，存储到 _rss 中
        if (!description.empty() || !content.empty())
        {
            _rss.emplace_back(title, link, description, content);
        }

        // 移动到下一个 <item> 节点
        itemNode = itemNode->NextSiblingElement("item");
    }
}

// 清洗文本内容的函数
string PageLib::cleanText(const string &text)
{
    static const regex reg("<[^>]+>|\r?\n|\\s+|'");
    return regex_replace(text, reg, "");
}

// 读取停止词
unordered_set<string> PageLib::readStopWord(const string &filename)
{
    unordered_set<string> stopWordList;

    // 打开文件
    ifstream ifs(filename);
    if (!ifs)
    {
        LogError("File error: Failed to open stop word file '" + filename + "' for reading.");
        return stopWordList; // 返回空集合
    }

    // 读取停止词
    string word;
    size_t wordCount = 0;
    while (getline(ifs, word))
    {
        stopWordList.insert(word);
        ++wordCount;
    }

    ifs.close();
    LogInfo("Successfully read " + to_string(wordCount) + " stop words from file: " + filename);
    return stopWordList;
}

// 网页去重函数
void PageLib::simHash()
{
    // 获取配置文件信息
    unordered_map<string, string> configMap = Configuration::getInstance()->getConfigMap();
    // 初始化 Simhasher 对象，加载词典、HMM 模型、IDF 文件和停用词表
    Simhasher simhaser(configMap["DICT_PATH"].c_str(),
                       configMap["HMM_PATH"].c_str(),
                       configMap["IDF_PATH"].c_str(),
                       configMap["STOP_WORD_PATH"].c_str());

    // 读取清洗后的每一篇文章，计算出指纹（SimHash 值）
    // 文章的内容可能在 _content 中，也可能在 _description 中
    vector<uint64_t> us;     // 存放每篇文章的 SimHash 值
    us.reserve(_rss.size()); // 预分配空间，避免频繁内存分配

    LogInfo("cal simhash start");
    // 遍历每篇文章，计算 SimHash 值
    for (auto &rss : _rss)
    {
        size_t topN = 5;  // 提取关键词的数量
        uint64_t u64 = 0; // 用于存储计算出的 SimHash 值

        // 如果文章内容不为空，则基于内容计算 SimHash 值
        if (!rss._content.empty())
        {
            simhaser.make(rss._content, topN, u64);
            us.push_back(u64);
        }
        // 如果文章内容为空但描述不为空，则基于描述计算 SimHash 值
        else if (!rss._description.empty())
        {
            simhaser.make(rss._description, topN, u64);
            us.push_back(u64);
        }
        // 如果内容和描述都为空，则使用默认值 0
        else
        {
            us.push_back(u64);
        }
    }
    LogInfo("cal simhash end");

    // 网页去重
    LogInfo("compare simhash start");
    unordered_set<uint64_t> hash_set; // 用于存储已经出现的 SimHash 值
    hash_set.reserve(_rss.size());    // 预分配空间，避免频繁内存分配
    size_t start = 0;                 // 记录去重后的网页数量

    // 遍历每篇文章的SimHash值，检查是否重复
    for (size_t i = 0; i < _rss.size(); ++i)
    {
        // 查找当前文章的 SimHash 值是否已经存在于 hash_set 中
        auto it = hash_set.find(us[i]);
        if (it == hash_set.end()) // 如果不存在，说明不是重复的网页
        {
            hash_set.insert(us[i]); // 将当前 SimHash 值插入 hash_set，防止重复
            if (start != i)         // 如果当前文章的位置不是 start，则交换位置
            {
                swap(_rss[start], _rss[i]);
            }
            ++start; // 增加去重后的网页数量
        }
        // 如果 SimHash 值已经存在，说明是重复的网页，直接跳过
    }
    LogInfo("compare simhash end");

    // 调整 _rss 的大小，只保留去重后的网页
    _rss.resize(start);
}

// 创建格式化网页
void PageLib::createFormatWebPage()
{
    // 预分配 _pages 的内存
    _pages.clear();
    _pages.reserve(_rss.size());
    LogInfo("Start creating formatted web pages. Total RSS items: " + to_string(_rss.size()));

    // 遍历清洗且去重后的每一篇网页
    size_t docid = 0;
    for (const auto &rss : _rss)
    {
        // 如果内容和描述都为空，则跳过
        if (rss._content.empty() && rss._description.empty())
        {
            ++docid;
            continue;
        }

        // 预分配一个足够大的字符串缓冲区
        std::string page;
        size_t estimatedSize = rss._title.size() + rss._link.size() +
                               (rss._content.empty() ? rss._description.size() : rss._content.size()) + 128; // 额外预留 128 字节
        page.reserve(estimatedSize);

        // 拼接字符串
        page.append("<doc><docid>").append(std::to_string(docid + 1)).append("</docid>").append("<title>").append(rss._title).append("</title>").append("<link>").append(rss._link).append("</link>");

        // 优先使用 content，如果 content 为空则使用 description
        if (!rss._content.empty())
        {
            page.append("<content>").append(rss._content).append("</content>");
        }
        else
        {
            page.append("<description>").append(rss._description).append("</description>");
        }

        // 闭合 doc 标签并换行
        page.append("</doc>\n");

        // 将生成的页面添加到 _pages
        _pages.push_back(std::move(page)); // 使用 std::move 避免拷贝
        ++docid;
    }
    LogInfo("Finished creating formatted web pages. Total pages created: " + to_string(_pages.size()));
}